﻿using System.Text;

namespace org.apache.lucene.analysis.de
{

	// This file is encoded in UTF-8

	/*
	 * Licensed to the Apache Software Foundation (ASF) under one or more
	 * contributor license agreements.  See the NOTICE file distributed with
	 * this work for additional information regarding copyright ownership.
	 * The ASF licenses this file to You under the Apache License, Version 2.0
	 * (the "License"); you may not use this file except in compliance with
	 * the License.  You may obtain a copy of the License at
	 *
	 *     http://www.apache.org/licenses/LICENSE-2.0
	 *
	 * Unless required by applicable law or agreed to in writing, software
	 * distributed under the License is distributed on an "AS IS" BASIS,
	 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	 * See the License for the specific language governing permissions and
	 * limitations under the License.
	 */

	/// <summary>
	/// A stemmer for German words. 
	/// <para>
	/// The algorithm is based on the report
	/// "A Fast and Simple Stemming Algorithm for German Words" by J&ouml;rg
	/// Caumanns (joerg.caumanns at isst.fhg.de).
	/// </para>
	/// </summary>
	public class GermanStemmer
	{
		/// <summary>
		/// Buffer for the terms while stemming them.
		/// </summary>
		private StringBuilder sb = new StringBuilder();

		/// <summary>
		/// Amount of characters that are removed with <tt>substitute()</tt> while stemming.
		/// </summary>
		private int substCount = 0;

		private static readonly Locale locale = new Locale("de", "DE");

		/// <summary>
		/// Stemms the given term to an unique <tt>discriminator</tt>.
		/// </summary>
		/// <param name="term">  The term that should be stemmed. </param>
		/// <returns>      Discriminator for <tt>term</tt> </returns>
		protected internal virtual string stem(string term)
		{
		  // Use lowercase for medium stemming.
		  term = term.ToLower(locale);
		  if (!isStemmable(term))
		  {
			return term;
		  }
		  // Reset the StringBuilder.
		  sb.Remove(0, sb.Length);
		  sb.Insert(0, term);
		  // Stemming starts here...
		  substitute(sb);
		  strip(sb);
		  optimize(sb);
		  resubstitute(sb);
		  removeParticleDenotion(sb);
		  return sb.ToString();
		}

		/// <summary>
		/// Checks if a term could be stemmed.
		/// </summary>
		/// <returns>  true if, and only if, the given term consists in letters. </returns>
		private bool isStemmable(string term)
		{
		  for (int c = 0; c < term.Length; c++)
		  {
			if (!char.IsLetter(term[c]))
			{
			  return false;
			}
		  }
		  return true;
		}

		/// <summary>
		/// suffix stripping (stemming) on the current term. The stripping is reduced
		/// to the seven "base" suffixes "e", "s", "n", "t", "em", "er" and * "nd",
		/// from which all regular suffixes are build of. The simplification causes
		/// some overstemming, and way more irregular stems, but still provides unique.
		/// discriminators in the most of those cases.
		/// The algorithm is context free, except of the length restrictions.
		/// </summary>
		private void strip(StringBuilder buffer)
		{
		  bool doMore = true;
		  while (doMore && buffer.Length > 3)
		  {
			if ((buffer.Length + substCount > 5) && StringHelperClass.SubstringSpecial(buffer, buffer.Length - 2, buffer.Length).Equals("nd"))
			{
			  buffer.Remove(buffer.Length - 2, buffer.Length - buffer.Length - 2);
			}
			else if ((buffer.Length + substCount > 4) && StringHelperClass.SubstringSpecial(buffer, buffer.Length - 2, buffer.Length).Equals("em"))
			{
				buffer.Remove(buffer.Length - 2, buffer.Length - buffer.Length - 2);
			}
			else if ((buffer.Length + substCount > 4) && StringHelperClass.SubstringSpecial(buffer, buffer.Length - 2, buffer.Length).Equals("er"))
			{
				buffer.Remove(buffer.Length - 2, buffer.Length - buffer.Length - 2);
			}
			else if (buffer[buffer.Length - 1] == 'e')
			{
			  buffer.Remove(buffer.Length - 1, 1);
			}
			else if (buffer[buffer.Length - 1] == 's')
			{
			  buffer.Remove(buffer.Length - 1, 1);
			}
			else if (buffer[buffer.Length - 1] == 'n')
			{
			  buffer.Remove(buffer.Length - 1, 1);
			}
			// "t" occurs only as suffix of verbs.
			else if (buffer[buffer.Length - 1] == 't')
			{
			  buffer.Remove(buffer.Length - 1, 1);
			}
			else
			{
			  doMore = false;
			}
		  }
		}

		/// <summary>
		/// Does some optimizations on the term. This optimisations are
		/// contextual.
		/// </summary>
		private void optimize(StringBuilder buffer)
		{
		  // Additional step for female plurals of professions and inhabitants.
		  if (buffer.Length > 5 && StringHelperClass.SubstringSpecial(buffer, buffer.Length - 5, buffer.Length).Equals("erin*"))
		  {
			buffer.Remove(buffer.Length - 1, 1);
			strip(buffer);
		  }
		  // Additional step for irregular plural nouns like "Matrizen -> Matrix".
		  // NOTE: this length constraint is probably not a great value, its just to prevent AIOOBE on empty terms
		  if (buffer.Length > 0 && buffer[buffer.Length - 1] == ('z'))
		  {
			buffer[buffer.Length - 1] = 'x';
		  }
		}

		/// <summary>
		/// Removes a particle denotion ("ge") from a term.
		/// </summary>
		private void removeParticleDenotion(StringBuilder buffer)
		{
		  if (buffer.Length > 4)
		  {
			for (int c = 0; c < buffer.Length - 3; c++)
			{
			  if (buffer.Substring(c, 4).Equals("gege"))
			  {
				buffer.Remove(c, c + 2 - c);
				return;
			  }
			}
		  }
		}

		/// <summary>
		/// Do some substitutions for the term to reduce overstemming:
		/// 
		/// - Substitute Umlauts with their corresponding vowel: äöü -> aou,
		///   "ß" is substituted by "ss"
		/// - Substitute a second char of a pair of equal characters with
		///   an asterisk: ?? -> ?*
		/// - Substitute some common character combinations with a token:
		///   sch/ch/ei/ie/ig/st -> $/§/%/&/#/!
		/// </summary>
		private void substitute(StringBuilder buffer)
		{
		  substCount = 0;
		  for (int c = 0; c < buffer.Length; c++)
		  {
			// Replace the second char of a pair of the equal characters with an asterisk
			if (c > 0 && buffer[c] == buffer[c - 1])
			{
			  buffer[c] = '*';
			}
			// Substitute Umlauts.
			else if (buffer[c] == 'ä')
			{
			  buffer[c] = 'a';
			}
			else if (buffer[c] == 'ö')
			{
			  buffer[c] = 'o';
			}
			else if (buffer[c] == 'ü')
			{
			  buffer[c] = 'u';
			}
			// Fix bug so that 'ß' at the end of a word is replaced.
			else if (buffer[c] == 'ß')
			{
				buffer[c] = 's';
				buffer.Insert(c + 1, 's');
				substCount++;
			}
			// Take care that at least one character is left left side from the current one
			if (c < buffer.Length - 1)
			{
			  // Masking several common character combinations with an token
			  if ((c < buffer.Length - 2) && buffer[c] == 's' && buffer[c + 1] == 'c' && buffer[c + 2] == 'h')
			  {
				buffer[c] = '$';
				buffer.Remove(c + 1, c + 3 - c + 1);
				substCount = + 2;
			  }
			  else if (buffer[c] == 'c' && buffer[c + 1] == 'h')
			  {
				buffer[c] = '§';
				buffer.Remove(c + 1, 1);
				substCount++;
			  }
			  else if (buffer[c] == 'e' && buffer[c + 1] == 'i')
			  {
				buffer[c] = '%';
				buffer.Remove(c + 1, 1);
				substCount++;
			  }
			  else if (buffer[c] == 'i' && buffer[c + 1] == 'e')
			  {
				buffer[c] = '&';
				buffer.Remove(c + 1, 1);
				substCount++;
			  }
			  else if (buffer[c] == 'i' && buffer[c + 1] == 'g')
			  {
				buffer[c] = '#';
				buffer.Remove(c + 1, 1);
				substCount++;
			  }
			  else if (buffer[c] == 's' && buffer[c + 1] == 't')
			  {
				buffer[c] = '!';
				buffer.Remove(c + 1, 1);
				substCount++;
			  }
			}
		  }
		}

		/// <summary>
		/// Undoes the changes made by substitute(). That are character pairs and
		/// character combinations. Umlauts will remain as their corresponding vowel,
		/// as "ß" remains as "ss".
		/// </summary>
		private void resubstitute(StringBuilder buffer)
		{
		  for (int c = 0; c < buffer.Length; c++)
		  {
			if (buffer[c] == '*')
			{
			  char x = buffer[c - 1];
			  buffer[c] = x;
			}
			else if (buffer[c] == '$')
			{
			  buffer[c] = 's';
			  buffer.Insert(c + 1, new char[]{'c', 'h'}, 0, 2);
			}
			else if (buffer[c] == '§')
			{
			  buffer[c] = 'c';
			  buffer.Insert(c + 1, 'h');
			}
			else if (buffer[c] == '%')
			{
			  buffer[c] = 'e';
			  buffer.Insert(c + 1, 'i');
			}
			else if (buffer[c] == '&')
			{
			  buffer[c] = 'i';
			  buffer.Insert(c + 1, 'e');
			}
			else if (buffer[c] == '#')
			{
			  buffer[c] = 'i';
			  buffer.Insert(c + 1, 'g');
			}
			else if (buffer[c] == '!')
			{
			  buffer[c] = 's';
			  buffer.Insert(c + 1, 't');
			}
		  }
		}

	}

}